-
Notifications
You must be signed in to change notification settings - Fork 334
WPB-20214: Add member searchability #4786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
bc31287
to
78ad65e
Compare
The below is now resolved. The current error when running the test is this:
From running this command: |
9891c56
to
6a1a1b8
Compare
6a1a1b8
to
4790b05
Compare
@akshaymankar Noting here what you asked about in the standup: the When we had all data in postgres though, the query could entirely be done on the database side: join |
@eyeinsky I think we shouldn't support this query param on |
4790b05
to
b0f782c
Compare
@akshaymankar I'm ready for another round of review! |
I think we can just lookup the team id of the targetted user and check whether the user who made the request is an admin of that team. We shouldn't need team id in the path.
No, we can expose the fact that his handle is taken as far as we don't tell who has this handle.
Yeah, this is the only location.
Ideally, yes. Sometimes we've been lazy, but the agreed upon rule was to move a test if you need to work on it and not to add more tests in the legacy test suites. |
libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/SearchResult_20TeamContact_user.hs
Outdated
Show resolved
Hide resolved
c0e5357
to
cfc7888
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a release note explaining how the search index mapping has to be updated and how to avoid downtime, etc.
And there are some minor comments, looks good overall.
integration/test/Test/Search.hs
Outdated
let -- Helper to change user searchability. | ||
setSearchable self uid searchable = do | ||
req <- baseRequest self Brig Versioned $ joinHttpPath ["users", uid, "searchable"] | ||
submit "POST" $ addJSON searchable req |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should go into API.Brig
integration/test/Test/Search.hs
Outdated
u1' <- BrigP.getUser u1 u1 >>= getJSON 200 | ||
assertBool "Searchable is still True" =<< (u1' %. "searchable" & asBool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If u1'
is not used later I would prefer to do the assertion in a bindResponse
body, to reduce number of ticked variable names. Also you could assert like this: u1 %. "searchable"
shouldMatch True
(I think) which I find more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above applies to the following lines, too.
:> QueryParam' | ||
[ Optional, | ||
Strict, | ||
Description "Optional, return only non-seacrhable members when false." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will it return only searchable members when true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would have passed the variable explicitly forward to ES, but that would have been confusing (?searchable=true
would have returned users where this flag is explicitly set to true), so it is now changed at the latest iteration 2ced710.
randomUserPrefix :: | ||
(MonadCatch m, MonadIO m, MonadHttp m, HasCallStack) => | ||
Text -> | ||
Brig -> | ||
m User | ||
randomUserPrefix prefix brig = do | ||
n <- fromName <$> randomName | ||
createUser' True (prefix <> n) brig | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems unused, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, removed!
833088a
to
d8fb31c
Compare
@battermann I've now adapted to all of the above comments in the latest state of this branch (7b3b5e5). Edit: Ah, changelog entry incoming. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements member searchability functionality by adding a searchable
field to users that controls their visibility in public search endpoints. When set to false, users won't be found through public search but remain accessible to team admins through dedicated endpoints.
Key changes:
- Added
searchable
boolean field to user data structures with default valuetrue
- Implemented
POST /users/:uid/searchable
API endpoint with team admin permission requirements - Modified search endpoints to filter based on searchability while preserving admin access
Reviewed Changes
Copilot reviewed 31 out of 36 changed files in this pull request and generated 5 comments.
Show a summary per file
File | Description |
---|---|
libs/wire-api/src/Wire/API/User.hs | Added profileSearchable and userSearchable fields to user data types |
libs/wire-api/src/Wire/API/Team/Member.hs | Added SetMemberSearchable permission for team admins |
libs/wire-api/src/Wire/API/Routes/Public/Brig.hs | Added API route for setting user searchability |
libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs | Implemented searchability filtering in local search and admin endpoint |
libs/wire-subsystems/src/Wire/IndexedUserStore/ElasticSearch.hs | Added Elasticsearch filtering for searchable users |
services/brig/src/Brig/Data/User.hs | Updated database schema and queries to include searchable field |
integration/test/Test/Search.hs | Added comprehensive integration tests for searchability feature |
changelog.d/2-features/WPB-20214 | Added feature changelog entry |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
services/brig/src/Brig/Data/User.hs
Outdated
managedBy = managedBy, | ||
supportedProtocols = prots | ||
supportedProtocols = prots, | ||
searchable = True -- NewUser doesn't have this field. TODO: should a defSearchable be added to Wire.API.User to be used everywhere? |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TODO comment suggests adding a default constant but leaves it unresolved. Consider either implementing the suggested defSearchable
constant in Wire.API.User or removing this TODO if the current approach is acceptable.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a40d240, we add it when we need it.
udSso = Nothing, | ||
udEmailUnvalidated = Nothing | ||
udEmailUnvalidated = Nothing, | ||
udSearchable = Nothing -- TODO: or Just True? |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TODO comment questions whether this should be Nothing
or Just True
. This uncertainty in test data could indicate unclear requirements. Consider resolving this based on the expected behavior for legacy data without the searchable field.
udSearchable = Nothing -- TODO: or Just True? | |
udSearchable = Nothing |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ffba78f, keeping it as Nothing as if the value didn't exist in Elastic Search.
-- | ||
-- If you ever think about adding a new permission flag, read Note | ||
-- [team roles] first. |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The comment about reading 'Note [team roles]' was moved from after the data type definition to before it, but the new SetMemberSearchable
permission was added without any indication that this note was consulted. Consider adding a comment confirming this note was reviewed for the new permission.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it was moved, it was definitely read. I moved it because it relates to the entire data type (as when it was next to a data constructor, it looked as if it only mattered to that particular one).
Assertions () -> | ||
m () | ||
(!!!) io = void . (<!!) io | ||
io !!! aa = void (io <!! aa) |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change from pattern matching to direct function composition appears to be unrelated to the searchability feature. Consider moving this refactoring to a separate commit or PR to maintain clear separation of concerns.
io !!! aa = void (io <!! aa) | |
m !!! a = do | |
_ <- m <!! a | |
pure () |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is part of a functionally no-op refactor commit. The single line version is shorter, but still clear.
type UserAPI = | ||
-- See Note [ephemeral user sideeffect] | ||
Named |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removal of 'See Note [ephemeral user sideeffect]' comments appears unrelated to the searchability feature. These documentation changes should be part of a separate commit focused on documentation cleanup.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These removals are in a separate commit.
This reverts commit 791a4cb.
Right, changed it back. |
@akshaymankar I fixed the last issue here 5acb9f7, and also test that the result for |
Now re-running CI after |
329316d
to
8e395fa
Compare
Bumping CI, the |
{ setSearchable :: Bool | ||
} | ||
deriving (Generic) | ||
deriving anyclass (Aeson.ToJSON, Aeson.FromJSON, S.ToSchema) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would create camelCase JSON fields. We use snake_case everywhere. Can you please use ToSchema
from Data.Schema
and then deriving ToJSON
, FromJSON
and S.ToSchema
.
integration/test/Test/Search.hs
Outdated
-- /teams/:tid/search and /teams/:tid/search?searchable=true both get all members, searchable and non-searchable | ||
noQueryParam <- | ||
BrigP.searchTeam admin [] `bindResponse` \resp -> do | ||
resp.status `shouldMatchInt` 200 | ||
docs <- resp.json %. "documents" >>= asList | ||
mapM (\m -> m %. "id" & asString) docs | ||
withQueryParam <- | ||
BrigP.searchTeam admin [("searchable", "true")] `bindResponse` \resp -> do | ||
resp.status `shouldMatchInt` 200 | ||
docs <- resp.json %. "documents" >>= asList | ||
mapM (\m -> m %. "id" & asString) docs | ||
assertBool "/teams/:tid/search and /teams/:tid/search?searchable=true are equal" | ||
$ Set.fromList noQueryParam | ||
== Set.fromList withQueryParam |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are not the same cases. When searchable=true
we return only searchable people, But when it is not set we should return all users. Otherwise there is no way for the admin to see all the users.
make sanitize-pr
one more typo
@akshaymankar I've now hopefully implemented the correct behavior to Also, on Friday I added a test for the legacy situation of where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thanks!
@battermann is on vacation, I've reviewed the work now.
Task checklist:
searchable
/users/:uid/searchable
API endpoint and by team admin, and nowhere elseSetMemberSearchable
/search/contacts
must filter based on searchable = True/teams/:tid/search
must expose a filter to find non-searchable users; otherwise ignore itsearchable
fieldexact handle search via internal endpoint should ignore this property, if no such endpoint exists, it should be createdWe'll do this in another task.Open questions
POST /users/:uid/searchable
, but as TeamId is required, then it currently isPOST /users/:uid/:tid/searchable
-- could this be improved?POST /handles
: should this endpoint also filter based on searchability?/search/contacts
to take searchability into account?Checklist
changelog.d